package com.ken.zeratul.strategy.aspect;

import cn.hutool.core.collection.ListUtil;
import com.ken.zeratul.commons.request.ServletRequestUtils;
import com.ken.zeratul.commons.spring.RegisterMethod;
import com.ken.zeratul.commons.spring.SpelUtils;
import com.ken.zeratul.strategy.annotation.Strategy;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 策略切面
 */
@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Slf4j
public class StrategyAspect {

    /**
     * 用来存放当前正在执行策略的方法全路径限定名，避免同一个策略方法反复进入AOP切面
     */
    private static ThreadLocal<Set<String>> threadLocal = new ThreadLocal<>();

    /**
     * 缓存某个类型的Bean集合
     */
    private static Map<Class, Collection<?>> cacheBeans = new ConcurrentHashMap();


    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 策略处理
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(strategy)")
    public Object strategyHandler(ProceedingJoinPoint joinPoint, Strategy strategy) throws Throwable {

        //获得代理的目标Bean对象
        Object target = joinPoint.getTarget();
        //获得目标Bean对象的方法
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();

        try {
            //判断是否重复进入策略
            boolean flag = isStrategy(method);
            if (flag) {
                return joinPoint.proceed();
            }

            //获取到合适的目标对象
            //先从缓存中获取
            Collection<?> serviceBeans = cacheBeans.get(target.getClass());
            //如果缓存为空，则再进行查询
            if (CollectionUtils.isEmpty(serviceBeans)) {
                Class targetCls = getStrategyTarget(target.getClass());
                Map<String, ?> allServiceBeanMap = applicationContext.getBeansOfType(targetCls);

                //如果没找到相同接口类型的Bean，则直接放行
                if (CollectionUtils.isEmpty(allServiceBeanMap)) {
                    return joinPoint.proceed();
                }

                //获取所有同类型的Bean对象
                serviceBeans = allServiceBeanMap.values();
                //进行优先级排序
                serviceBeans = sortBean(serviceBeans);
                //将对应的处理Bean对象集合放入缓存
                cacheBeans.put(target.getClass(), serviceBeans);
            }

            Object defaultBean = null;
            Method defaultMethod = null;
            Object runBean = null;
            Method runMethod = null;

            //循环处理所有同接口类型的Bean对象
            for (Object serviceBean : serviceBeans) {
                Method beanMethod = null;
                try {
                    //获取这个Bean的和目标Bean的相同方法
                    beanMethod = serviceBean.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
                } catch (Exception e) {}
                //如果没有找到相同的Bean，则跳过
                if (Objects.isNull(beanMethod)) continue;

                //获取策略注解
                Strategy strategyAnnotation = AnnotationUtils.findAnnotation(beanMethod, Strategy.class);
                //如果没有策略注解则跳过当前Bean的处理
                if (Objects.isNull(strategyAnnotation)) continue;

                //定义解析方法
                RegisterMethod registerMethod = new RegisterMethod();
                registerMethod.setRegisterMethodObjCls(ServletRequestUtils.class);
                registerMethod.setMethodName("requestParam");
                registerMethod.setExecMethodName("params");
                registerMethod.addParamClass(String.class);

                //获取表达式
                String el = strategyAnnotation.el();
                if (el.equals("default")) {
                    defaultBean = serviceBean;
                    defaultMethod = beanMethod;
                } else if (SpelUtils.parseExpression(el, serviceBean, null, null, method, joinPoint.getArgs(), ListUtil.toList(registerMethod), Boolean.class, false)){
                    runBean = serviceBean;
                    runMethod = beanMethod;
                    break;
                }
            }

            return Objects.isNull(runBean) ?
                    (Objects.isNull(defaultBean) ? joinPoint.proceed() : defaultMethod.invoke(defaultBean, joinPoint.getArgs())) :
                    runMethod.invoke(runBean, joinPoint.getArgs());
        } catch (InvocationTargetException e) {
            //反射会对业务异常进行包装，这里如果发生了反射调用异常，则直接返回反射的核心异常信息
            throw e.getTargetException();
        } finally {
            clear(method);
        }
    }

    /**
     * 查找到合适的Bean对象
     * @param targetcls
     * @return
     */
    private Class getStrategyTarget(Class targetcls){
        //如果父类是target，则直接返回当前类型
        if (targetcls.getSuperclass() == Object.class) return targetcls;

        Primary annotation = (Primary) targetcls.getAnnotation(Primary.class);
        if (!Objects.isNull(annotation)) {
            return targetcls;
        }

        return getStrategyTarget(targetcls.getSuperclass());
    }


    /**
     * 判断该策略方法是否第一次进入Aop切面，如果不是第一次，则直接跳过切面直接运行业务方法
     * @param method
     * @return
     */
    private boolean isStrategy(Method method) {
        Set<String> strSet = threadLocal.get();
        if (CollectionUtils.isEmpty(strSet)) {
            strSet = new HashSet<>();
            threadLocal.set(strSet);
        }

        //获取方法名称
        String name = createName(method);
        if (strSet.contains(name)) {
            //返回true，表示已经存在了
            return true;
        }
        strSet.add(name);
        return false;
    }

    /**
     * 根据类名 + 方法名称 + 形参类型名称 构建一个字符串，用来唯一标识该方法
     * @param method
     * @return
     */
    private String createName(Method method){
        StringBuilder stringBuilder = new StringBuilder();
        //获取方法名称
        String name = method.getName();
        stringBuilder.append(name).append("#");
        //获取形参类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes != null && parameterTypes.length > 0) {
            for (Class<?> parameterType : parameterTypes) {
                stringBuilder.append(parameterType.getName()).append("#");
            }
        }
        return stringBuilder.toString();
    }


    /**
     * 对Bean集合做一个排序操作
     * @param beans
     */
    private Collection<?> sortBean(Collection<?> beans){
        if (CollectionUtils.isEmpty(beans)) return null;
        //创建一个优先级高的集合
        Collection<Object> priorityBeans = new TreeSet<>((o1, o2) -> {
            Order order1 = AnnotationUtils.findAnnotation(o1.getClass(), Order.class);
            Order order2 = AnnotationUtils.findAnnotation(o2.getClass(), Order.class);
            return order1.value() - order2.value();
        });
        //创建一个普通优先级的集合
        Collection<Object> normalBeans = new ArrayList<>();

        for (Object bean : beans) {
            if (AnnotationUtils.findAnnotation(bean.getClass(), Order.class) != null) {
                priorityBeans.add(bean);
            } else {
                normalBeans.add(bean);
            }
        }

        //总的Collections
        Collection<Object> collection = new ArrayList<>();
        collection.addAll(priorityBeans);
        collection.addAll(normalBeans);
        return collection;
    }

    /**
     * 清空ThreadLocal资源
     */
    private void clear(Method method){
        Set<String> strSet = threadLocal.get();
        //如果Set集合为空，则回收ThreadLocal资源
        if (CollectionUtils.isEmpty(strSet)) {
            threadLocal.remove();
            return;
        }
        strSet.remove(createName(method));
    }

    /**
     * 清空缓存
     */
    public static void cacheClear(){
        cacheBeans.clear();
    }
}
